Understanding Multiple Inheritance and Method Resolution Order (MRO) in Python
Course: Python Training
Topic: Understanding Multiple Inheritance and Method Resolution Order (MRO)
Course Link: Python Training Course at JustAcademy
Demo Link: Register for a Free Course Demo
Website: Visit JustAcademy
1. Introduction to Multiple Inheritance
Inheritance is one of the fundamental concepts of Object-Oriented Programming (OOP) in Python. It allows a class to acquire attributes and methods from another class. Multiple inheritance is a type of inheritance in which a single child class inherits from two or more parent classes.
Multiple inheritance is useful when a class needs to combine functionality from different sources. For example, a SmartPhone class might inherit calling functionality from a Phone class and photography functionality from a Camera class.
However, when multiple parent classes contain methods with the same name, Python needs a consistent way to decide which method should be executed. Python uses the Method Resolution Order (MRO) to determine the order in which classes are searched.
2. Learning Objectives
- Understand the meaning and purpose of multiple inheritance.
- Create a class that inherits from multiple parent classes.
- Understand how Python handles methods with identical names in different classes.
- Learn the meaning and importance of Method Resolution Order (MRO).
- Explore the C3 linearization algorithm at a practical level.
- Use
mro() and __mro__ to inspect the resolution order.
- Understand how
super() works with multiple inheritance.
- Identify common problems and follow best practices.
3. What Is Multiple Inheritance?
Multiple inheritance allows one class to inherit methods and attributes from more than one parent class. The child class can use functionality from all its parent classes, subject to normal attribute lookup rules.
Basic Syntax
class ParentOne:
pass
class ParentTwo:
pass
class Child(ParentOne, ParentTwo):
pass
In this example, Child inherits from both ParentOne and ParentTwo. The order of the parent classes in the class definition is significant because it influences the Method Resolution Order.
Example: Multiple Inheritance
class Camera:
def take_photo(self):
print("Taking a photo")
class Phone:
def make_call(self):
print("Making a phone call")
class SmartPhone(Camera, Phone):
def browse_internet(self):
print("Browsing the internet")
device = SmartPhone()
device.take_photo()
device.make_call()
device.browse_internet()
Output:
Taking a photo
Making a phone call
Browsing the internet
Explanation: The SmartPhone class inherits from both Camera and Phone. Therefore, its objects can call methods defined in both parent classes, as well as methods defined directly in SmartPhone.
4. Why Is Multiple Inheritance Used?
Multiple inheritance can be useful when a class needs to combine several independent capabilities without duplicating code.
| Use Case |
Description |
Example |
| Combining capabilities |
Bring together different sets of functionality. |
A SmartPhone combines phone and camera features. |
| Code reuse |
Reuse methods from multiple existing classes. |
A reporting class uses logging and formatting methods. |
| Mixins |
Add small, focused behaviors to a class. |
A class receives serialization or logging behavior. |
| Shared interfaces |
Combine compatible behaviors from different base classes. |
A component supports several independent capabilities. |
Although multiple inheritance can be powerful, it should be used carefully. If the relationships between parent classes are unclear, the resulting code may become difficult to understand and maintain.
5. Understanding Method Resolution Order (MRO)
Method Resolution Order (MRO) is the order in which Python searches classes when looking for a method or attribute. It becomes especially important when a class inherits from multiple parents or when multiple classes define a method with the same name.
Python uses the C3 linearization algorithm to calculate the MRO. This algorithm creates a consistent ordering that respects the inheritance hierarchy and the order of base classes, while ensuring that a class appears only once in the resulting order.
Example: Identical Method Names
class ParentA:
def show(self):
print("Method from ParentA")
class ParentB:
def show(self):
print("Method from ParentB")
class Child(ParentA, ParentB):
pass
obj = Child()
obj.show()
Output:
Method from ParentA
Explanation: The class declaration is class Child(ParentA, ParentB). Python checks Child first, then searches ParentA before ParentB. Because ParentA defines show(), Python executes that implementation and does not continue searching for the same method.
If the parent order were changed to class Child(ParentB, ParentA), the MRO would normally place ParentB before ParentA, and ParentB.show() would be called instead.
6. How Python Calculates MRO
Python's C3 linearization algorithm builds the method resolution order while preserving important ordering constraints:
- Child before parent: A class appears before its base classes.
- Base-class ordering: The order of base classes in a class definition is respected when possible.
- Monotonicity: The established order of classes in a parent hierarchy remains consistent when that hierarchy is extended.
- No repeated classes: A class is included only once in the linearized order.
For ordinary attribute lookup, Python searches the resulting MRO from left to right. The first matching attribute or method found is used.
Example: Inspecting the MRO
class A:
pass
class B:
pass
class C(A, B):
pass
print(C.mro())
Output:
[<class '__main__.C'>,
<class '__main__.A'>,
<class '__main__.B'>,
<class 'object'>]
Explanation: The MRO begins with C, followed by A and B, and ends with Python's built-in object class. The exact module label may differ depending on where the code runs.
7. Using mro() and __mro__
Python provides two convenient ways to inspect a class's Method Resolution Order.
Method 1: Using mro()
class A:
pass
class B:
pass
class C(A, B):
pass
print(C.mro())
The mro() method returns a list containing the class and its ancestors in resolution order.
Method 2: Using __mro__
print(C.__mro__)
The __mro__ attribute provides the same ordering as a tuple. It is useful when inspecting class metadata or debugging inheritance behavior.
| Feature |
mro() |
__mro__ |
| Type of result |
List |
Tuple |
| Purpose |
Returns the class resolution order. |
Exposes the class resolution order. |
| Access |
ClassName.mro() |
ClassName.__mro__ |
8. Understanding the Diamond Problem
The diamond problem occurs when a class inherits from two classes that share a common ancestor. The inheritance diagram resembles a diamond, and the shared ancestor may define methods that are accessible through both branches.
Inheritance Structure
A
/ \
B C
\ /
D
In this structure, both B and C inherit from A, while D inherits from both B and C.
Example: Diamond Inheritance
class A:
def show(self):
print("A")
class B(A):
def show(self):
print("B")
super().show()
class C(A):
def show(self):
print("C")
super().show()
class D(B, C):
def show(self):
print("D")
super().show()
obj = D()
obj.show()
print([cls.__name__ for cls in D.mro()])
Output:
D
B
C
A
['D', 'B', 'C', 'A', 'object']
Explanation: The MRO is D → B → C → A → object. The call to super() inside D.show() continues to B.show(). The next super() call continues to C.show(), and the following one reaches A.show(). This demonstrates how cooperative method calls can follow one consistent order instead of repeatedly calling the shared ancestor through separate branches.
9. Understanding super() in Multiple Inheritance
The built-in super() function provides a way to call the next implementation of a method according to the current class's MRO. It does not simply mean “call my direct parent.” In multiple inheritance, the next class may be another parent or a class elsewhere in the MRO.
Example
class A:
def greet(self):
print("Hello from A")
class B(A):
def greet(self):
print("Hello from B")
super().greet()
class C(A):
def greet(self):
print("Hello from C")
super().greet()
class D(B, C):
def greet(self):
print("Hello from D")
super().greet()
D().greet()
Output:
Hello from D
Hello from B
Hello from C
Hello from A
Explanation: Each class calls super().greet() to continue to the next implementation in the MRO. This cooperative pattern allows each class to participate once in the method-call chain.
Important Rules for super()
- Use
super() to follow the MRO rather than hard-coding a parent class.
- For cooperative methods, ensure participating classes call
super() appropriately.
- Method signatures should be compatible across the cooperative call chain.
- In complex inheritance trees, inspect the MRO when method execution is unexpected.
10. Multiple Inheritance with Constructors
Constructors are commonly implemented using the __init__() method. When multiple parent classes define constructors, initialization should be designed carefully so that each class receives the arguments it needs and participates correctly in the MRO.
Example: Cooperative Initialization
class A:
def __init__(self, **kwargs):
print("Initializing A")
super().__init__(**kwargs)
class B(A):
def __init__(self, **kwargs):
print("Initializing B")
super().__init__(**kwargs)
class C(A):
def __init__(self, **kwargs):
print("Initializing C")
super().__init__(**kwargs)
class D(B, C):
def __init__(self, **kwargs):
print("Initializing D")
super().__init__(**kwargs)
obj = D()
Output:
Initializing D
Initializing B
Initializing C
Initializing A
Explanation: Each constructor delegates initialization to the next class in the MRO. The shared base class A is reached once through the cooperative chain. This approach is useful in complex class hierarchies, but all participating constructors must be designed to cooperate.
In real projects, avoid passing arbitrary arguments through the hierarchy unless each class handles its own parameters correctly. A class should consume the arguments it owns and pass only the remaining arguments onward.
11. Example: Combining Employee Roles
Multiple inheritance can combine focused capabilities in a business application. For example, an employee might have reporting and auditing functionality.
class ReportGenerator:
def generate_report(self):
print("Generating employee report")
class Auditor:
def audit_records(self):
print("Auditing employee records")
class ComplianceEmployee(ReportGenerator, Auditor):
def show_role(self):
print("Employee responsible for reports and audits")
employee = ComplianceEmployee()
employee.show_role()
employee.generate_report()
employee.audit_records()
Output:
Employee responsible for reports and audits
Generating employee report
Auditing employee records
Explanation: The ComplianceEmployee class combines the behavior of two independent classes. Since the parent classes provide different method names, there is no method-name conflict in this example.
12. Method Name Conflicts and Attribute Lookup
When two or more parent classes define the same method or attribute, Python uses the MRO to determine which definition is found first. The same principle applies to normal instance attribute lookup, although instance attributes and descriptors introduce additional details to Python's full attribute-access rules.
Example: Resolving a Conflict
class Printer:
def device_type(self):
print("Printer")
class Scanner:
def device_type(self):
print("Scanner")
class MultiFunctionDevice(Printer, Scanner):
pass
device = MultiFunctionDevice()
device.device_type()
print([cls.__name__ for cls in MultiFunctionDevice.mro()])
Output:
Printer
['MultiFunctionDevice', 'Printer', 'Scanner', 'object']
Explanation: Python finds device_type() in Printer before checking Scanner. If the parent order is reversed, the result changes accordingly.
13. Common Errors and Troubleshooting
| Problem |
Cause |
Solution |
| Unexpected method is executed |
A different parent appears earlier in the MRO. |
Inspect ClassName.mro() and check the base-class order. |
| A method in another parent is never called |
An earlier class defines the method and the method chain does not continue. |
Use cooperative super() calls where appropriate. |
| Constructor raises an argument error |
Classes in the chain accept incompatible arguments. |
Design compatible constructors and pass only the remaining arguments. |
| Cannot create a class due to inconsistent MRO |
The requested parent order conflicts with inheritance constraints. |
Review the hierarchy and remove contradictory base-class ordering. |
| Shared base behavior runs unexpectedly or more than once |
Methods explicitly call parent implementations instead of following a cooperative chain. |
Use a consistent MRO-aware design with super(). |
| Code is difficult to maintain |
Too many parent classes or unclear responsibilities. |
Prefer focused mixins, composition, or a simpler hierarchy. |
14. Multiple Inheritance vs. Multilevel Inheritance
| Feature |
Multiple Inheritance |
Multilevel Inheritance |
| Meaning |
One child inherits from two or more parents. |
A class inherits from another derived class in a chain. |
| Structure |
One class has multiple direct base classes. |
Classes form a sequence of inheritance levels. |
| Example |
class C(A, B): |
class C(B): where B inherits from A. |
| Primary concern |
Resolving behavior from several parents. |
Understanding inherited behavior across levels. |
| MRO complexity |
Can be more complex due to multiple base classes. |
Often simpler when the hierarchy is a single chain. |
15. Best Practices for Multiple Inheritance
- Keep parent classes focused: Each parent should represent a clear responsibility or capability.
- Prefer mixins for reusable behavior: A mixin should generally add a specific feature rather than represent a complete standalone entity.
- Understand the MRO: Use
mro() or __mro__ when class lookup becomes difficult to follow.
- Use cooperative
super(): Avoid hard-coding calls to specific parent classes when you need the full MRO chain to work together.
- Keep method signatures compatible: Cooperative methods should agree on how arguments are passed.
- Avoid unnecessary complexity: If a class hierarchy is hard to explain, consider composition instead.
- Test method conflicts: Write tests for the behavior expected when parent classes share method names.
- Document unusual hierarchies: Explain why multiple inheritance is needed and how the method chain works.
16. Practical Exercises
Exercise 1: Basic Multiple Inheritance
Create two classes, MusicPlayer and VideoPlayer. Give each class a method that plays its respective media type. Create a MediaDevice class that inherits from both and test both methods.
Exercise 2: Method Resolution Order
Create two parent classes that both define a method named display(). Create a child class that inherits from both parents. Print the MRO and determine which implementation executes.
Exercise 3: Diamond Inheritance
Build the hierarchy A, B(A), C(A), and D(B, C). Use super() in each class and observe the method-call order.
Exercise 4: Cooperative Constructors
Create a hierarchy with multiple parent classes whose constructors accept keyword arguments. Use super() to initialize each class and ensure every argument is handled by the correct class.
Exercise 5: MRO Investigation
Choose a class that uses multiple inheritance. Print its MRO and explain why each class appears in that order. Test how changing the base-class order affects method lookup.
17. Interview Questions and Answers
Q1. What is multiple inheritance in Python?
Answer: Multiple inheritance is an OOP feature that allows one class to inherit attributes and methods from two or more parent classes.
Q2. What is Method Resolution Order (MRO)?
Answer: MRO is the order Python follows when searching classes for a method or attribute. Python uses the C3 linearization algorithm to calculate this order.
Q3. How can you view a class's MRO?
Answer: Use ClassName.mro() or inspect ClassName.__mro__.
Q4. What happens when two parent classes define the same method?
Answer: Python searches according to the child's MRO and uses the first matching method it finds.
Q5. What is the diamond problem?
Answer: It occurs when a class inherits from two classes that share a common ancestor. Python's MRO provides a consistent lookup order through this hierarchy.
Q6. Does super() always call the direct parent class?
Answer: No. It delegates to the next class in the MRO, which may not be the direct parent named in the inheritance structure.
Q7. What is C3 linearization?
Answer: C3 linearization is the algorithm Python uses to calculate an MRO that respects inheritance constraints and the ordering of base classes.
Q8. Can the order of base classes affect method execution?
Answer: Yes. Changing the base-class order can change the MRO and therefore which implementation Python finds first.
Q9. When should multiple inheritance be avoided?
Answer: Avoid it when it creates confusing dependencies, conflicting behavior, or an unnecessarily complicated hierarchy. Composition may be a clearer alternative.
Q10. What is a mixin?
Answer: A mixin is a class designed to add a focused behavior to another class, often through inheritance, rather than to represent a complete standalone object.
18. Quick Revision Checklist
- Multiple inheritance means inheriting from more than one parent class.
- Python searches classes according to the Method Resolution Order.
- The C3 linearization algorithm calculates the MRO.
mro() returns the resolution order as a list.
__mro__ exposes the resolution order as a tuple.
- The first matching method found in the MRO is normally used.
super() follows the MRO rather than simply naming a direct parent.
- Cooperative methods and constructors must be designed to work together.
- Mixins can add focused capabilities to classes.
- Composition may be preferable when multiple inheritance becomes too complex.
19. Conclusion
Multiple inheritance allows Python classes to combine functionality from several parent classes, promoting code reuse and flexible object-oriented design. However, when parent classes contain methods with identical names or share ancestors, understanding the Method Resolution Order is essential.
Python's C3 linearization algorithm provides a consistent lookup order, while mro(), __mro__, and super() help developers inspect and work with that order. By keeping classes focused, designing cooperative methods carefully, and testing inheritance behavior, developers can use multiple inheritance effectively without introducing unnecessary complexity.
Learn Python with JustAcademy
Strengthen your understanding of Python programming, object-oriented concepts, inheritance, and practical coding through structured learning.
Explore the Python Training Course
Register for a Course Demo
Visit the JustAcademy Website